﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace SMSDemo
{
    public class SMSUtil
    {
        /// <summary>
        /// 配置 AccessKey ID
        /// </summary>
        public string AccessKeyId { get; set; } = "";
        /// <summary>
        /// 配置 AccessKey Secret
        /// </summary>
        public string AccessKeySecret { get; set; } = "";
        /// <summary>
        /// 是否自动重试
        /// </summary>
        public bool AutoRetry { get; set; } = true;
        /// <summary>
        /// 地区
        /// </summary>
        public string RegionId { get; set; } = "cn-hangzhou";
        /// <summary>
        /// 版本
        /// </summary>
        public string Version { get; set; } = "2017-05-25";
        /// <summary>
        /// 执行动作
        /// </summary>
        public string Action { get; set; } = "SendSms";
        /// <summary>
        ///  访问的域名
        /// </summary>
        public string Domain { get; set; } = "dysmsapi.aliyuncs.com";
        /// <summary>
        /// 
        /// </summary>
        private const string CONST_FORMAT = "JSON";
        /// <summary>
        /// 超时时间（毫秒)
        /// </summary>
        private const int CONST_TIMEOUTIMEOUTINMILLISECONDS = 100000;
        /// <summary>
        /// 最大重试次数
        /// </summary>
        private const int CONST_MAXRETRY_NUMBER = 3;

        /// <summary>
        /// 发送短信
        /// </summary>
        public async Task<(bool success, string response)> Send(SMSEntity smsEntity)
        {
            Dictionary<string, string> paramers = new Dictionary<string, string>();
            paramers.Add("PhoneNumbers", smsEntity.PhoneNumbers);
            paramers.Add("SignName", smsEntity.SignName);
            paramers.Add("TemplateCode", smsEntity.TemplateCode);
            paramers.Add("TemplateParam", JsonConvert.SerializeObject(smsEntity.TemplateParam));
            paramers.Add("OutId", smsEntity.OutId);
            paramers.Add("AccessKeyId", this.AccessKeyId);

            try
            {
                int retryTimes = 1;
                string baseUrl = GetRequestUrl(paramers, this.AccessKeySecret);
                var client = await HttpGetAsync(baseUrl, CONST_TIMEOUTIMEOUTINMILLISECONDS);
                while (500 <= client.StatusCode && AutoRetry && retryTimes < CONST_MAXRETRY_NUMBER)
                {
                    baseUrl = GetRequestUrl(paramers, AccessKeySecret);
                    client = await HttpGetAsync(baseUrl, CONST_TIMEOUTIMEOUTINMILLISECONDS);
                    retryTimes++;
                }
                // 
                if (!string.IsNullOrEmpty(client.response))
                {
                    var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(client.response);
                    if (res != null && res.ContainsKey("Code") && "OK".Equals(res["Code"]))
                    {
                        return (true, response: client.response);
                    }
                }
                return (false, response: client.response);
            }
            catch (Exception exception)
            {
                return (false, response: exception.Message);
            }
        }
        /// <summary>
        /// 构建请求地址
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="accessSecret"></param>
        /// <returns></returns>
        internal string GetRequestUrl(Dictionary<string, string> parameters, string accessSecret)
        {
            Dictionary<string, string> imutableParameters = new Dictionary<string, string>(parameters);
            imutableParameters.Add("Timestamp", GetTimestamp(DateTime.Now));
            imutableParameters.Add("SignatureMethod", "HMAC-SHA1");
            imutableParameters.Add("SignatureVersion", "1.0");
            imutableParameters.Add("SignatureNonce", Guid.NewGuid().ToString());
            imutableParameters.Add("Action", Action);
            imutableParameters.Add("Version", Version);
            imutableParameters.Add("Format", CONST_FORMAT);
            imutableParameters.Add("RegionId", RegionId);

            IDictionary<string, string> sortedParameters = new SortedDictionary<string, string>(imutableParameters, StringComparer.Ordinal);
            StringBuilder canonicalizedQueryString = new StringBuilder();
            foreach (var entry in sortedParameters)
            {
                canonicalizedQueryString.AppendFormat("&{0}={1}", entry.Key, entry.Value);
            }

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("GET");
            stringBuilder.Append("&");
            stringBuilder.Append(PercentEncode("/"));
            stringBuilder.Append("&");
            stringBuilder.Append(PercentEncode(canonicalizedQueryString.ToString().Substring(1)));

            string signature = SignString(stringBuilder.ToString(), string.Format("{0}{1}", accessSecret, "&"));

            imutableParameters.Add("Signature", signature);
            return UrlBuilder(Domain, imutableParameters);
        }
        /// <summary>
        /// 构建请求地址
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        internal string UrlBuilder(string endpoint, Dictionary<string, string> parameters)
        {
            StringBuilder stringBuilder = new StringBuilder("");
            stringBuilder.Append("http://").Append(endpoint);
            if (-1 == stringBuilder.ToString().IndexOf("?"))
            {
                stringBuilder.Append("/?");
            }
            string queryString = ConcatQueryString(parameters);
            return stringBuilder.Append(queryString).ToString();
        }
        /// <summary>
        /// 规范请求参数
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        internal string ConcatQueryString(Dictionary<string, string> parameters)
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (!(parameters == null || parameters.Count == 0))
            {
                foreach (var entry in parameters)
                {
                    if (stringBuilder.Length == 0)
                    {
                        stringBuilder.AppendFormat("{0}={1}", HttpUtility.UrlEncode(entry.Key), HttpUtility.UrlEncode(entry.Value, Encoding.UTF8));
                        continue;
                    }
                    stringBuilder.AppendFormat("&{0}={1}", HttpUtility.UrlEncode(entry.Key), HttpUtility.UrlEncode(entry.Value, Encoding.UTF8));
                }
            }
            return stringBuilder.ToString();
        }
        /// <summary>
        ///  获取符合 ISO8601 要求的时间戳
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        internal string GetTimestamp(DateTime dateTime)
        {
            return dateTime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.CreateSpecificCulture("en-US"));
        }
        /// <summary>
        /// 签名
        /// </summary>
        internal string SignString(string source, string accessSecret)
        {
            using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(accessSecret.ToCharArray())))
            {
                return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(source.ToCharArray())));
            }
        }
        /// <summary>
        /// 参数名称和参数值进行URL编码
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        internal string PercentEncode(string value)
        {
            StringBuilder stringBuilder = new StringBuilder();
            string text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
            byte[] bytes = Encoding.GetEncoding("UTF-8").GetBytes(value);
            foreach (char c in bytes)
            {
                if (text.IndexOf(c) >= 0)
                {
                    stringBuilder.Append(c);
                    continue;
                }
                stringBuilder.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c));
            }
            return stringBuilder.ToString();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="timeoutInMilliSeconds"></param>
        /// <returns></returns>
        private async Task<(int StatusCode, string response)> HttpGetAsync(string url, int timeoutInMilliSeconds)
        {
            HttpClientHandler handler = new HttpClientHandler();
            handler.Proxy = null;
            handler.AutomaticDecompression = DecompressionMethods.GZip;
            using (var http = new HttpClient(handler))
            {
                http.Timeout = new TimeSpan(TimeSpan.TicksPerMillisecond * timeoutInMilliSeconds);
                HttpResponseMessage response = await http.GetAsync(url);
                return ((int)response.StatusCode, await response.Content.ReadAsStringAsync());
            }
        }
    }
}
